package app.util;

import app.database.CrubUtils;
import app.util.algo.AStar;
import app.util.algo.JPS;
import app.env.Data;
import app.agent.Schedule;
import app.env.Position;
import app.env.ShowGui;
import jade.core.AID;
import jade.lang.acl.ACLMessage;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.*;

/**
 * @Name: AppUtils
 * @Description: 一些工具方法
 * @Author: colfish
 * @Date: 2021/4/15 21:43
 * 好好学习，天天向上！
 */
public class AppUtils {

    private static Logger logger = LogManager.getLogger(AppUtils.class);
    /**
     * 判断坐标是否在目标代理的双倍影响范围内，确保两个目标代理不会互相影响
     * @param position 新生成的目标代理位置
     * @param targetPositions 已生成的目标代理位置集合
     * @param range 目标代理的影响范围
     * @return false表示不会冲突, true表示会冲突
     */
    public static boolean inTargetRange(Position position, Collection<Position> targetPositions, int range){
        if(targetPositions.size()==0){
            return false;
        }
        for(Position p : targetPositions){
            if(p.getDistance(position) <= 2*range)
                return true;
        }
        return false;
    }

    public static String logMessage(AID sender, AID receiver, ACLMessage message){
        return "sender:<" + sender.getLocalName() + ">; receiver:<" + receiver.getLocalName() + ">; Message: " + message.getContent();
    }

    /**
     * 找到目标代理上下左右四个目标点位置
     * @param targetPosition 目标代理位置
     * @return 四个目标点位置
     */
    public static Position[] findSurroundTarget(Position targetPosition){
        Position[] positions = new Position[4];
        int range = Schedule.targetDetectRange;

        positions[0] = new Position(targetPosition.x - range, targetPosition.y);
        positions[1] = new Position(targetPosition.x + range, targetPosition.y);
        positions[2] = new Position(targetPosition.x, targetPosition.y - range);
        positions[3] = new Position(targetPosition.x, targetPosition.y + range);

        return positions;
    }

    /**
     * 在一个位置map中找到距离目标点最近的代理名称
     * @param tarPosition 目标点
     * @param positions 位置map
     * @return 距离目标点最近的代理名称
     */
    public static String findMinTarget(Position tarPosition, Map<String, Position> positions) {
        String minName = null;
        double minDis = Integer.MAX_VALUE;
        for(Map.Entry<String, Position> position : positions.entrySet()){
            if(minDis > tarPosition.getDistance(position.getValue())){
                minDis = tarPosition.getDistance(position.getValue());
                minName = position.getKey();
            }
        }
        return minName;
    }

    /**
     * 没有使用特殊算法规划路径, 返回移动后的位置
     * ui界面移动搜索代理，更新ui界面
     */
    public static Position moveAgent1(String agentName, Position srcPos, Position desPos){
        int dx = 1;
        int dy = 1;
        if(desPos.x - srcPos.x==0) dx = 0;
        else if(desPos.x - srcPos.x < 0) dx = -1;
        if(desPos.y - srcPos.y==0) dy = 0;
        else if(desPos.y - srcPos.y < 0) dy = -1;

        // 记录移动的轨迹
        Position position = new Position(srcPos.x, srcPos.y);
        ArrayList<Position> trackPart1 = new ArrayList<>();
//        trackPart1.add(position);
        int temp = 0;
        for(int i = 1 ; i <= Math.abs(desPos.x - srcPos.x); i++){
            if(ShowGui.board[position.x+dx][position.y]== Data.OBSTACLE_SIGNAL){
                logger.info("I encounter the obstacle!");
                break;
            }
            ShowGui.board[position.x][position.y] = temp;
            position.x += dx;
            temp = ShowGui.board[position.x][position.y];
            ShowGui.board[position.x][position.y] = Data.SEARCH_SIGNAL;
            trackPart1.add(new Position(position.x, position.y));
            ShowGui.updateGui();
        }
        for(int j = 1; j <= Math.abs(desPos.y - srcPos.y); j++){
            if(ShowGui.board[position.x][position.y+dy]==Data.OBSTACLE_SIGNAL){
                logger.info("I encounter the obstacle!");
                break;
            }
            ShowGui.board[position.x][position.y] = temp;
            position.y += dy;
            temp = ShowGui.board[position.x][position.y];
            ShowGui.board[position.x][position.y] = Data.SEARCH_SIGNAL;
            trackPart1.add(new Position(position.x, position.y));
            ShowGui.updateGui();
        }
        new CrubUtils().saveTracks(agentName, trackPart1);
        return position;
    }

    // 使用A*算法规划路径
    public static void moveAgent(String agentName, Position srcPos, Position desPos){
        // 如果目标点是屏障信号值，则直接停止搜索代理，不需要移动
        if(ShowGui.board[desPos.x][desPos.y]==Data.OBSTACLE_SIGNAL){
            ShowGui.board[srcPos.x][srcPos.y] = Data.SEARCH_STOP_SIGNAL;
            return;
        }
//        long stime = System.currentTimeMillis();
        Stack<Position> trackPart = AStar.findPath(ShowGui.board, srcPos.x, srcPos.y, desPos.x, desPos.y);
//        long etime = System.currentTimeMillis();
//        long gap = etime-stime;
//        System.out.println("A* time:" +gap);
        ArrayList<Position> trackPart1 = new ArrayList<>();
        if(trackPart!= null){
            Position position = trackPart.peek();
            while (!trackPart.isEmpty()){
                position = trackPart.pop();
                trackPart1.add(position);
                ShowGui.board[position.x][position.y] = Data.SEARCH_SIGNAL;

                ShowGui.updateGui();
            }
            ShowGui.board[position.x][position.y] = Data.SEARCH_STOP_SIGNAL;
            new CrubUtils().saveTracks(agentName, trackPart1);
            logger.info("Move finished!");
        }
    }

    /*使用JPS算法进行路径规划*/
    public static void moveAgent2(String agentName, Position srcPos, Position desPos, int state){
        // 如果目标点是屏障信号值，则直接停止搜索代理，不需要移动
        if(ShowGui.board[desPos.x][desPos.y]==Data.OBSTACLE_SIGNAL){
            ShowGui.board[srcPos.x][srcPos.y] = Data.SEARCH_STOP_SIGNAL;
            return;
        }
//        long stime = System.currentTimeMillis();
        List<Position> trackPart = new JPS(srcPos.x, srcPos.y, desPos.x, desPos.y, ShowGui.board).findPath();
//        long etime = System.currentTimeMillis();
//        long gap = etime-stime;
//        System.out.println("JPS time:" +gap);
        ArrayList<Position> trackPart1 = new ArrayList<>();

        if(trackPart!=null){
            for (Position position : trackPart ) {
                trackPart1.add(position);
                ShowGui.board[position.x][position.y] = Data.SEARCH_SIGNAL;
                ShowGui.updateGui();
            }
            if(state == 0){
                Position position = trackPart.get(trackPart.size() - 1);
                ShowGui.board[position.x][position.y] = Data.SEARCH_STOP_SIGNAL;
            }
            new CrubUtils().saveTracks(agentName, trackPart1);
//            Data.searchAgentTracks.put(agentName, trackPart1);
            logger.info("Move finished!");
        }
    }


    /**
     * 判断目标代理是否被包围，探测周围上下左右range距离位置的信号是否全为搜索代理的信号
     * @param position 目标代理位置
     * @return true表示被包围
     */
    public static boolean isSurrounded(Position position){
        int x = position.x;
        int y = position.y;
        int range = Schedule.targetDetectRange;
        // 判断是停止的搜索代理或是障碍物
        return ShowGui.board[x + range][y] <= Data.SEARCH_STOP_SIGNAL &&
                ShowGui.board[x - range][y] <= Data.SEARCH_STOP_SIGNAL &&
                ShowGui.board[x][y + range] <= Data.SEARCH_STOP_SIGNAL &&
                ShowGui.board[x][y - range] <= Data.SEARCH_STOP_SIGNAL;
    }

    /**
     * 连接被包围的目标代理上下左右四个点，并更新ui
     * @param position 目标代理位置
     */
    public static void DrawLine(Position position){
        // TODO 画线
    }

    /**
     * 在一个坐标点周围range范围内随机找到一个有效点，作为搜索代理随机移动的目标
     * @param position 坐标点
     * @param limit 新生成点的最小距离
     * @param range 范围
     * @return 随机生成的有效点
     */
    public static Position getRandomPosition(Position position, int limit, int range){
        Random random = new Random();
        // 随机生成[-range, range)的坐标偏移
        int x = position.x;
        int y = position.y;
        int dx = random.nextInt(2*range+1)-range;
        int dy = random.nextInt(2*range+1)-range;
        // 坐标点加偏移量的新坐标在边界范围内。并且新坐标对应的信号不是障碍物和目标点
        while ( x+dx < 0 || x+dx >= Data.MAP_HEIGHT ||
                y+dy < 0 || y+dy >= Data.MAP_WIDTH ||
                ShowGui.board[x+dx][y+dy] == Data.TARGET_SIGNAL ||
                ShowGui.board[x+dx][y+dy] == Data.OBSTACLE_SIGNAL ||
                position.getDistance(x+dx, y+ dy) <= limit) {
            dx = random.nextInt(2*range+1)-range;
            dy = random.nextInt(2*range+1)-range;
        }
        return new Position(x+dx, y+dy);
    }

    /**
     * 找到搜索代理搜索范围内信号值最大的坐标
     * @param position
     * @param range
     * @return
     */
    public static Position getMaxPos(Position position, int range) {
        Position retPos = new Position();
        int maxSig = 0;
        for(int i = position.x-range;i < position.x + range; i++){
            for(int j = position.y-range;j < position.y + range; j++){
                if( i==position.x&&j==position.y
                    ||position.getDistance(i,j)>range
                    ||i < 0 || i >= Data.MAP_HEIGHT
                    ||j < 0 || j >= Data.MAP_WIDTH
                    ||ShowGui.board[i][j]==Data.SEARCH_SIGNAL)
                    continue;
                if(maxSig < ShowGui.board[i][j]){
                    maxSig = ShowGui.board[i][j];
                    retPos.x = i;
                    retPos.y = j;
                }
            }
        }
        return retPos;
    }

    public void saveData(){
//        Data.searchAgentTracks

    }
}
